"""
路线规划异步任务
"""
try:
    from celery import shared_task
except Exception:
    # Celery 未安装时提供空装饰器以避免导入错误
    def shared_task(func=None, **kwargs):
        def decorator(f):
            return f
        return decorator(func) if func else decorator
from django.conf import settings
from django.core.cache import cache
from apps.attractions.models import Attraction
from apps.routes.models import RoutePlan
from apps.routes.algorithms import TravelDijkstra, MultiFactorWeight, TSPOptimizer
from utils.distance_calculator import DistanceCalculator
from utils.number_formatter import format_price, format_distance, format_coordinate


def _minutes_to_time_str(minutes: int) -> str:
    hours = minutes // 60
    mins = minutes % 60
    return f"{hours:02d}:{mins:02d}"


def _get_weight_config(priority):
    if priority == 'distance':
        return MultiFactorWeight(distance_weight=0.8, cost_weight=0.2)
    elif priority == 'cost':
        return MultiFactorWeight(distance_weight=0.3, cost_weight=0.7)
    else:
        return MultiFactorWeight(distance_weight=0.6, cost_weight=0.4)


def _build_graph(attraction_ids, transport_mode='driving'):
    graph = {}
    calculator = DistanceCalculator()
    sorted_ids = sorted(attraction_ids)
    distance_cache_key = f'distance_matrix_{"_".join(map(str, sorted_ids))}_{transport_mode}'
    cached_matrix = cache.get(distance_cache_key)
    attractions = {attr.id: attr for attr in Attraction.objects.filter(id__in=attraction_ids)}
    for origin_id in attraction_ids:
        graph[origin_id] = {}
        for dest_id in attraction_ids:
            if origin_id == dest_id:
                continue
            distance_data = None
            if cached_matrix and origin_id in cached_matrix and dest_id in cached_matrix[origin_id]:
                distance_data = cached_matrix[origin_id][dest_id]
            else:
                distance_data = calculator.calculate_distance(origin_id, dest_id, transport_mode=transport_mode)
            dest_attr = attractions[dest_id]
            transport_cost = float(distance_data.get('transport_cost', 0))
            ticket_price = float(dest_attr.ticket_price or 0)
            total_cost = transport_cost + ticket_price
            graph[origin_id][dest_id] = {
                'distance': float(format_distance(distance_data.get('distance', 0))),
                'cost': float(format_price(total_cost)),
                'time': distance_data.get('travel_time', 0) + (dest_attr.visit_time or 60),
                'popularity': dest_attr.popularity or 0,
                'transport_mode': transport_mode
            }
    if not cached_matrix:
        distance_matrix = {oid: {} for oid in attraction_ids}
        for origin_id in attraction_ids:
            for dest_id in attraction_ids:
                if origin_id != dest_id:
                    distance_matrix[origin_id][dest_id] = graph[origin_id][dest_id]
        cache.set(distance_cache_key, distance_matrix, 86400)
    return graph


def _build_route_details(route_ids, graph, start_time_str=None, attractions_info=None):
    """
    构建路线详情
    函数说明：根据图数据与起始时间生成到达/离开时间，并在需要时添加等待开门的时间（含跨天开放窗口）
    """
    details = []
    attractions = attractions_info or {attr.id: attr for attr in Attraction.objects.filter(id__in=route_ids)}
    current_time_minutes = 0
    if start_time_str:
        try:
            parts = start_time_str.split(':')
            current_time_minutes = int(parts[0]) * 60 + int(parts[1])
        except Exception:
            current_time_minutes = 0
    for i in range(len(route_ids)):
        attraction_id = route_ids[i]
        attraction = attractions.get(attraction_id)
        if not attraction:
            continue
        visit_time = attraction.visit_time if attraction.visit_time else 60
        transport_time = 0
        if i > 0:
            prev_id = route_ids[i-1]
            if prev_id in graph and attraction_id in graph[prev_id]:
                edge_data = graph[prev_id][attraction_id]
                transport_time = edge_data.get('time', 0)
        arrival_time_minutes = current_time_minutes + transport_time
        original_arrival_minutes = arrival_time_minutes
        wait_time = 0
        if attraction.open_time and attraction.close_time:
            open_minutes = attraction.open_time.hour * 60 + attraction.open_time.minute
            close_minutes = attraction.close_time.hour * 60 + attraction.close_time.minute
            day_minutes = arrival_time_minutes % 1440
            if close_minutes >= open_minutes:
                if day_minutes < open_minutes:
                    wait_time = open_minutes - day_minutes
                elif day_minutes > close_minutes:
                    # 当天已过关门，等待到次日开门
                    wait_time = (1440 - day_minutes) + open_minutes
            else:
                # 跨天窗口（例如 22:00-02:00），开放区间为 [open, 1440) ∪ [0, close]
                if not (day_minutes >= open_minutes or day_minutes <= close_minutes):
                    if day_minutes < open_minutes:
                        wait_time = open_minutes - day_minutes
                    else:
                        wait_time = (1440 - day_minutes) + open_minutes
            arrival_time_minutes += wait_time
        departure_time_minutes = arrival_time_minutes + visit_time
        arrival_time_str = _minutes_to_time_str(arrival_time_minutes)
        departure_time_str = _minutes_to_time_str(departure_time_minutes)
        detail = {
            'attraction_id': attraction_id,
            'attraction_name': attraction.name,
            'latitude': float(format_coordinate(attraction.latitude)),
            'longitude': float(format_coordinate(attraction.longitude)),
            'ticket_price': float(format_price(attraction.ticket_price)),
            'visit_time': visit_time,
            'order': i + 1,
            'arrival_time': arrival_time_str,
            'departure_time': departure_time_str,
            'open_time': attraction.open_time.strftime('%H:%M') if attraction.open_time else None,
            'close_time': attraction.close_time.strftime('%H:%M') if attraction.close_time else None,
            'wait_time': wait_time,
            'arrival_before_wait': _minutes_to_time_str(original_arrival_minutes) if wait_time > 0 else None,
        }
        if i > 0:
            prev_id = route_ids[i-1]
            if prev_id in graph and attraction_id in graph[prev_id]:
                edge_data = graph[prev_id][attraction_id]
                detail['from_previous'] = {
                    'distance': float(format_distance(edge_data.get('distance', 0))),
                    'cost': float(format_price(edge_data.get('cost', 0))),
                    'time': transport_time
                }
        details.append(detail)
        current_time_minutes = departure_time_minutes
    return details


@shared_task(bind=True)
def plan_route_task(self, payload: dict):
    """
    异步路线规划任务
    函数说明：与同步规划逻辑一致，返回同样的数据结构，供前端轮询使用
    """
    budget = float(payload.get('budget', 0))
    start_attraction_id = int(payload.get('start_attraction_id'))
    must_visit = payload.get('must_visit', []) or []
    priority = payload.get('priority', 'balanced')
    transport_mode = payload.get('transport_mode', 'driving')
    start_time_str = payload.get('start_time')  # 字符串 HH:MM
    max_total_time = payload.get('max_total_time')
    user_id = payload.get('user_id')

    must_visit_sorted = sorted(must_visit) if must_visit else []
    time_key = f'{start_time_str}_{max_total_time}' if start_time_str or max_total_time else 'no_time'
    cache_key = f'route_plan_{start_attraction_id}_{"_".join(map(str, must_visit_sorted))}_{budget}_{priority}_{transport_mode}_{time_key}'
    cached_result = cache.get(cache_key)
    if cached_result:
        return cached_result

    # 数据准备
    attraction_ids = [start_attraction_id] + must_visit
    attractions = Attraction.objects.filter(id__in=attraction_ids)
    if attractions.count() != len(attraction_ids):
        raise ValueError('部分景点不存在')

    graph = _build_graph(attraction_ids, transport_mode)
    attractions_dict = {attr.id: attr for attr in Attraction.objects.filter(id__in=attraction_ids)}
    weight_config = _get_weight_config(priority)

    optimized_must_visit = must_visit
    if len(must_visit) > 1:
        tsp_optimizer = TSPOptimizer(graph, weight_config)
        optimized_order = tsp_optimizer.optimize_visit_order(start_attraction_id, must_visit)
        optimized_must_visit = [x for x in optimized_order if x != start_attraction_id]

    dijkstra = TravelDijkstra(
        graph,
        budget,
        weight_config,
        start_time=start_time_str,
        max_total_time=max_total_time,
        attractions_info=attractions_dict
    )
    optimal_route_ids, total_weight, total_cost, total_distance = dijkstra.find_optimal_routes(start_attraction_id, optimized_must_visit)
    route_details = _build_route_details(optimal_route_ids, graph, start_time_str=start_time_str, attractions_info=attractions_dict)
    total_wait_time = sum(int(d.get('wait_time', 0) or 0) for d in route_details)

    route_plan = RoutePlan.objects.create(
        user_id=user_id,
        budget=budget,
        start_attraction_id=start_attraction_id,
        must_visit_attractions=must_visit,
        transport_mode=transport_mode,
        optimal_route={
            'route': optimal_route_ids,
            'details': route_details
        },
        total_cost=total_cost,
        total_distance=total_distance
    )

    total_time = 0
    if route_details:
        last_detail = route_details[-1]
        if last_detail.get('departure_time') and start_time_str:
            dep_h, dep_m = map(int, last_detail['departure_time'].split(':'))
            st_h, st_m = map(int, start_time_str.split(':'))
            total_time = (dep_h * 60 + dep_m) - (st_h * 60 + st_m)

    response_data = {
        'id': route_plan.id,
        'budget': float(format_price(budget)),
        'start_attraction_id': start_attraction_id,
        'must_visit_attractions': must_visit,
        'transport_mode': transport_mode,
        'start_time': start_time_str,
        'max_total_time': max_total_time,
        'optimal_route': {
            'route': optimal_route_ids,
            'details': route_details,
            'total_wait_time': total_wait_time
        },
        'total_cost': float(format_price(total_cost)),
        'total_distance': float(format_distance(total_distance)),
        'total_time': total_time,
        'total_wait_time': total_wait_time,
        'created_at': route_plan.created_at.isoformat()
    }

    cache.set(cache_key, response_data, settings.CACHE_TIMEOUT.get('route_results', 3600))
    return response_data